<?php

namespace Xrfg\Api\Trading\Spot\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
use Xrfg\Api\Trading\Spot\Events\TradeSavedEvent;
use Xrfg\Core\Trading\Account\Models\Account;
use Xrfg\Core\Trading\Spot\Models\Order;
use Xrfg\Core\Trading\Spot\Models\Trade;
use Xrfg\Api\Trading\Spot\Repositories\OrderBook;
use Xrfg\Core\Trading\SystemAccount\Models\SystemAccount;

class MatchJob implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    private $symbol;
    private $config;
    /**
     * @var Order
     */
    private $buy;
    /**
     * @var Order
     */
    private $sell;
    /**
     * @var Trade
     */
    private $trade;

    public function __construct(string $symbol)
    {
        $this->symbol = $symbol;
        $this->config = config('pairs.'.$symbol);
    }

    public function handle(OrderBook $orderBook)
    {
        list($marketBuy, $limitBuy, $marketSell, $limitSell) = $orderBook->get($this->symbol);
        // 买卖单都有 // 撮合
        if (($marketBuy || $limitBuy) && ($marketSell || $limitSell)) {
            $this->match($orderBook, $marketBuy, $limitBuy, $marketSell, $limitSell);
        }

        // 有市价无限价 // 撤销市价单
        if ($marketBuy) {
            CancelOrderJob::dispatch($marketBuy)->onQueue('cancel');
        }
        if ($marketSell) {
            CancelOrderJob::dispatch($marketSell)->onQueue('cancel');
        }

        // 无操作
        return;
    }

    private function match(OrderBook $orderBook, $marketBuy, $limitBuy, $marketSell, $limitSell)
    {
        try {
            DB::transaction(function () use ($orderBook, $marketBuy, $limitBuy, $marketSell, $limitSell) {
                // 获取买单
                $this->buy = Order::lockForUpdate()->find($marketBuy ?: $limitBuy);
                // 检查买单
                if (
                    $this->symbol != $this->buy->symbol ||
                    Order::SIDE_BUY != $this->buy->side ||
                    Order::STATUS_NEW != $this->buy->status
                ) {
                    $orderBook->remove($this->buy);
                    MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);

                    return;
                }
                // 获取卖单
                $this->sell = Order::lockForUpdate()->find($marketSell ?: $limitSell);
                // 检查卖单
                if (
                    $this->sell->symbol != $this->symbol ||
                    Order::SIDE_SELL != $this->sell->side ||
                    Order::STATUS_NEW != $this->sell->status
                ) {
                    $orderBook->remove($this->sell);
                    MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);

                    return;
                }

                if (Order::TYPE_LIMIT == $this->buy->type && Order::TYPE_LIMIT == $this->sell->type) {
                    // 判断买卖单价格能否成交
                    if ($this->buy->price < $this->sell->price) {
                        return;
                    }

                    // 成交量
                    $number = min(bcsub($this->buy->number, $this->buy->deal_number), bcsub($this->sell->number, $this->sell->deal_number));
                    // 成交价 成交方向
                    if ($this->buy->id >= $this->sell->id) {
                        $price = $this->sell->price;
                        $side = $this->sell->side;
                    } else {
                        $price = $this->buy->price;
                        $side = $this->buy->side;
                    }
                    // 成交金额
                    $total = bcmul($price, $number);
                } else {
                    if (Order::TYPE_MARKET == $this->buy->type) { // 买单是市价单，卖单是限价单
                        // 成交价格为卖单价格
                        $price = $this->sell->price;
                        $side = $this->sell->side;
                        // 当前价格买单最大可成交数数量
                        $maxNumber = bcdiv(bcsub($this->buy->number, $this->buy->deal_number), $price, $this->config['number_precision']);
                        if (0 == $maxNumber) {
                            CancelOrderJob::dispatch($this->buy->id)->onQueue('cancel');
                            MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);

                            return;
                        }
                        // 当前成交数量
                        $number = min($maxNumber, bcsub($this->sell->number, $this->sell->deal_number));
                        // 成交金额
                        $total = bcmul($price, $number);
                        if (0 == $total) {
                            CancelOrderJob::dispatch($this->buy->id)->onQueue('cancel');
                            MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);

                            return;
                        }
                    } else { // 买单是限价单，卖单是市价单
                        // 成交价格为买单价格
                        $price = $this->buy->price;
                        $side = $this->buy->side;
                        // 当前卖单最大可成交数数量
                        $maxNumber = bcsub($this->sell->number, $this->sell->deal_number);
                        if (0 == $maxNumber) {
                            CancelOrderJob::dispatch($this->buy->id)->onQueue('cancel');
                            MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);

                            return;
                        }
                        // 当前成交数量
                        $number = min($maxNumber, bcsub($this->buy->number, $this->buy->deal_number));
                        // 成交金额
                        $total = bcmul($price, $number);
                        if (0 == $total) {
                            CancelOrderJob::dispatch($this->buy->id)->onQueue('cancel');
                            MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);

                            return;
                        }
                    }
                }

                if (Order::TYPE_MARKET == $this->buy->type) {
                    $this->buy->deal_number = bcadd($this->buy->deal_number, $total);
                } else {
                    $this->buy->deal_number = bcadd($this->buy->deal_number, $number);
                }
                if ($this->buy->deal_number == $this->buy->number) {
                    $this->buy->status = Order::STATUS_FILLED;
                }
                $this->buy->total = bcadd($this->buy->total, $total);

                $this->sell->deal_number = bcadd($this->sell->deal_number, $number);
                if ($this->sell->deal_number == $this->sell->number) {
                    $this->sell->status = Order::STATUS_FILLED;
                }
                $this->sell->total = bcadd($this->sell->total, $total);

                // 买方手续费
                $buyFee = bcmul($number, $this->buy->fee);
                // 卖方手续费
                $sellFee = bcmul($total, $this->sell->fee);

                // 生成凭证
                $this->trade = new Trade();
                $this->trade->symbol = $this->symbol;
                $this->trade->side = $side;
                $this->trade->coin_id = $this->buy->coin_id;
                $this->trade->market_id = $this->buy->market_id;
                $this->trade->buy_user_id = $this->buy->user_id;
                $this->trade->sell_user_id = $this->sell->user_id;
                $this->trade->buy_order_id = $this->buy->id;
                $this->trade->sell_order_id = $this->sell->id;
                $this->trade->price = $price;
                $this->trade->number = $number;
                $this->trade->buy_fee = $buyFee;
                $this->trade->sell_fee = $sellFee;
                $this->trade->created_at = now();

                $this->buy->save();
                $this->sell->save();
                $this->trade->save();

                /** 买方入账户 @var Account $buyInAccount */
                $buyInAccount = Account::lockForUpdate()
                    ->where('user_id', $this->buy->user_id)
                    ->where('coin_id', $this->buy->coin_id)
                    ->first();
                /** 买方出账户 @var Account $buyOutAccount */
                $buyOutAccount = Account::lockForUpdate()
                    ->where('user_id', $this->buy->user_id)
                    ->where('coin_id', $this->buy->market_id)
                    ->first();
                if ($this->buy->user_id == $this->sell->user_id) {
                    $sellOutAccount = &$buyInAccount;
                    $sellInAccount = &$buyOutAccount;
                } else {
                    /** 卖方入账户 @var Account $sellInAccount */
                    $sellInAccount = Account::lockForUpdate()
                        ->where('user_id', $this->sell->user_id)
                        ->where('coin_id', $this->sell->market_id)
                        ->first();
                    /** 卖方出账户 @var Account $sellOutAccount */
                    $sellOutAccount = Account::lockForUpdate()
                        ->where('user_id', $this->sell->user_id)
                        ->where('coin_id', $this->sell->coin_id)
                        ->first();
                }
                /** 买方手续费账户 @var SystemAccount $buyFeeAccount */
                $buyFeeAccount = SystemAccount::lockForUpdate()
                    ->where('account_type', SystemAccount::TYPE_TRADE_FEE)
                    ->where('coin_id', $this->buy->coin_id)
                    ->first();
                /** 卖方手续费 @var SystemAccount $sellFeeAccount */
                $sellFeeAccount = SystemAccount::lockForUpdate()
                    ->where('account_type', SystemAccount::TYPE_TRADE_FEE)
                    ->where('coin_id', $this->sell->market_id)
                    ->first();

                // 生成资金流水记录
                if (Order::TYPE_LIMIT == $this->buy->type && $this->buy->price > $price) {
                    $difference = bcmul($number, bcsub($this->buy->price, $price));
                    $buyOutAccount->availableIncrement($difference);
                    $buyOutAccount->disabledDecrement($difference);
                    $buyOutAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_DEAL_SPREAD];
                    $buyOutAccount->save();
                }

                $buyOutAccount->disabledDecrement($total);
                $buyOutAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_BUY_DEAL_OUT];
                $buyOutAccount->save();

                $sellInAccount->availableIncrement(bcsub($total, $sellFee));
                $sellInAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_SELL_DEAL_IN];
                $sellInAccount->save();

                $sellOutAccount->disabledDecrement($number);
                $sellOutAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_SELL_DEAL_OUT];
                $sellOutAccount->save();

                $buyInAccount->availableIncrement(bcsub($number, $buyFee));
                $buyInAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_BUY_DEAL_IN];
                $buyInAccount->save();

                if ($buyFee > 0) {
                    $buyFeeAccount->numberIncrement($buyFee);
                    $buyFeeAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_BUY_DEAL_IN];
                    $buyFeeAccount->save();
                }

                if ($sellFee > 0) {
                    $sellFeeAccount->numberIncrement($sellFee);
                    $sellFeeAccount->proofId = [$this->trade->id, Trade::CLASS_ID, Trade::OP_SELL_DEAL_IN];
                    $sellFeeAccount->save();
                }
            });
        } catch (Throwable $throwable) {
            Log::emergency('@@@@@@@@@@@@@@@@@@@@撮合失败@@@@@@@@@@@@@@@@@@@@');
            Log::emergency('买单：'.$this->buy->toJson());
            Log::emergency('卖单：'.$this->sell->toJson());
            Log::emergency($throwable);

            return;
        }

        if ($this->trade) {
            KlineJob::dispatch($this->trade)->onQueue('kline');
            event(new TradeSavedEvent($this->trade));
            $orderBook->update($this->buy);
            $orderBook->update($this->sell);
            MatchJob::dispatch($this->symbol)->onQueue('match:'.$this->symbol);
        }
    }
}
